Skip to content

fix(security): Filesystem grants derived from the inner command must not follow interpreter or symlink content that lives where the confined process can write, and each derived directory needs a bounded taint decision. - #446

Open
github-actions[bot] wants to merge 5 commits into
mainfrom
fix/security-20261031-080207-scheduled-ok-plan-03

Conversation

@github-actions

Copy link
Copy Markdown

Issue: Refs #321

What

  • Sandbox launches no longer derive filesystem read grants from the inner command without bounds: symlink chains and shebang interpreters resolved from agent-writable surface cannot extend the sandbox's read surface.
  • Newly derived read access into a home's sensitive config/credential trees is refused unless the user granted it explicitly.
  • Security regression tests cover both boundaries on the Linux and macOS backends.

Why

  • The inner command often lives where the confined process can write (workdir, temp dirs), so an unbounded derivation would let content there influence what the next sandbox may read — a security gap tracked in the linked issue.

How

  • resolveInnerBinaryDirs now takes a policy built from the grants (derivedGrantPolicyForGrants, internal/sandboxrun/resolve_binary.go) and returns every path the resolution visited; refuseDerivedGrants checks each derived directory against it and fails the launch closed when a rule is crossed.
  • Taint rule: a command, symlink hop, or interpreter on writable surface only launches when the derivation stays within already-granted paths; the error tells the user to move the tool or grant the paths explicitly.
  • Home rule: a derived directory at or below a home's sensitive trees is refused for every launch.
  • Both backends pass the policy in BuildChildArgv (internal/sandboxrun/backend_linux.go, internal/sandboxrun/backend_darwin.go).

Verification

  • go build, go vet, gofmt, and staticcheck pass.
  • The security regression tests (internal/sandboxrun/derived_grants_security_test.go plus the updated backend and resolver tests) pass.

Follow-up

  • None.

Test Writer Agent and others added 4 commits October 31, 2026 09:09
Signed-off-by: Test Writer Agent <test-writer@security-remediate.invalid>
Signed-off-by: Test Writer Agent <test-writer@security-remediate.invalid>
…not follow interpreter or symlink content that lives where the confined process can write, and each derived directory needs a bounded taint decision.

Signed-off-by: Fix Writer Agent <fix-writer@security-remediate.invalid>
…not follow interpreter or symlink content that lives where the confined process can write, and each derived directory needs a bounded taint decision.

Signed-off-by: Fix Writer Agent <fix-writer@security-remediate.invalid>
@github-actions github-actions Bot added the do-not-merge Hold: automated fix PR, needs a human pass label Oct 31, 2026
@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] Needs fix at d65a666ca56862231fd494a7add05d5167af95fa; compared with current main b752d098116a49c4c12480e552ee296e85c5ff57 (no sandbox-area drift).

  • P1 — resolve_binary.go:211–218: <workdir>/prefix/bin/tool plus prefix/lib -> ~/.config/private is classified as already granted by its literal workdir spelling. Actual macOS BuildChildArgv succeeds and emits a new read grant on the sensitive target. Replacing an initially benign support link between validation and profile generation also widens access. Check target containment and avoid re-resolving redundant mutable aliases.
  • P1 — resolve_binary.go:343–366: directory-component symlinks disappear from taint history. stable/alias -> <workdir>/redirect -> ungranted-dir admits new grants for command, interpreter, and support-directory variants. Walk/check every component, not just the final file and resolved endpoint.
  • P2 — resolve_binary.go:212–214 / pathCoveredBy:575: / does not cover descendants ("//" prefix), so explicit root grants and --learn can newly refuse launches. The corresponding current-main control passes.
  • P2 — resolve_binary.go:273–285: the home guard also accepts a grant on a home ancestor and, on this case-insensitive macOS filesystem, .CONFIG as an alias of .config.

Verification: existing non-integration sandboxrun/sandboxprofile tests pass with -race; reviewer counterexamples fail as expected, while nvm/pyenv/Homebrew/wrapper controls pass. Vet and Linux cross-compilation pass. Full targeted integration failures reproduce on main; nested Seatbelt is prohibited, so live backend enforcement is not verified. These security bypasses remain from main rather than being newly introduced; the root-grant refusal is new.

CI is action_required, with no jobs; DCO alone is green. Do not merge: repair the boundaries, retain regression tests, and obtain successful real-backend CI plus human approval.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] LOCAL / NOT PUSHED: repair prepared for F1–F5 on d65a666ca56862231fd494a7add05d5167af95fa (Refs #321): target-only containment, complete ancestor/support-link history, literal derived lowering without redundant mutable aliases, //learn-mode coverage, recursive home overlap, and filesystem-identity case handling.

Regression and compatibility controls pass; affected race run: 305 pass / 2 skip events. Vet and Linux cross-compilation pass. Full fixed/pristine runs have identical 25 integration failures under outer-sandbox restrictions; Linux runtime was not exercised. Pending fresh independent review and human push. Not merge-ready.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] Independent re-review of LOCAL patch c43664156863 against d65a666ca5: NEEDS ANOTHER FIX. Hash checked, all seven files independently applied and byte-verified. The remote PR head is unchanged; this does not claim the repair was pushed.

  • F1–F5 counterexamples pass locally, including alias replacement, ancestor provenance, root/learn, home overlap and case aliases.
  • Two new P2 Linux regressions: canonical grant coverage can omit the mount for stage2’s real executable when the explicit read/write/allow grant is a symlink; canonical-only derived mounts also remove ancestor-alias names still used by env-wrapped commands and shebang interpreters. All five fresh namespace cases fail locally and pass on pristine head. Preserve checked target/destination mappings without restoring unchecked mutable alias binds.
  • Go 1.26: affected race suite 305 pass / 2 skip, vet and Linux cross-compilation pass. Full targeted runs have the same 25 sandbox-limited failures as pristine head. Namespace/argv evidence is not live Linux execution.

Remaining gates: another repair/re-review, human push, successful exact-head CI (current run still action_required, zero jobs), and live bwrap/Seatbelt plus case-sensitive-filesystem tests. No approval or merge-ready claim.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] LOCAL / NOT PUSHED, R2: cumulative repair addresses F6/F7 while retaining F1–F5 and compatibility controls. Missing Linux execution spellings now link only to already-mounted namespace paths, without new alias host binds; conflicting layouts fail closed.

Red/green namespace cases pass. Affected race run: 317 pass / 2 skip events; vet, Linux compilation, and actual 10-file patch application/byte checks pass. Full runs retain the same 25 outer-sandbox integration failures as pristine head. The native Linux execution gate is added but unexecuted here (macOS, no bwrap). Fresh independent review and human push remain pending; this is not merge approval.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] Independent review of LOCAL cumulative candidate 6aa26608865e against d65a666ca5: NEEDS ANOTHER FIX. The remote head is unchanged; this is not a pushed repair or merge approval.

  • Original F1–F7 cases pass locally, but resolve_binary.go:476–494 loses current/bin in shims/tool -> ../current/bin/tool, current -> versions/v1; env and shebang launches lose their executable/interpreter. Retain checked spellings introduced by symlink targets, not just the initial and final paths.
  • bwrap.go:65–82 drops runtime mounts under root read/write authorization although only allow-root is actually mounted, producing a new pre-launch refusal. Base omission decisions on emitted namespace coverage; learn-mode controls still pass.

All 10 patch files independently hash/applied/byte-verified. Supplied affected race suite: 317 pass / 2 skip; the final suite retains four fresh failing subcases, while all five pristine controls pass. Vet/Linux compilation pass; full fixed/pristine runs share the same 25 outer-sandbox failures.

Linux runtime is pending: Darwin host, no bwrap; compile/model checks are not execution. Preserve all regressions and require all five TestIntegrationDerivedGrantNamespaceExecution cases to actually run, plus fresh repro coverage. Remaining gates: another repair/re-review, human-only push, successful exact-head CI (currently action_required, zero jobs), live Seatbelt and case-sensitive tests. Not merge-ready.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] LOCAL / NOT PUSHED, R3: cumulative repair for F8/F9 on d65a666ca5 is available as pr-446-fix-r3.patch (7661ada4fcb3).

  • Checked plans now retain every symlink-target spelling that env or a shebang still follows, restoring current/bin only as a namespace-local link to the checked mount.
  • Linux omits a derived bind only when the authorizing grant is actually emitted: root read/write retain the narrow command mount; root allow remains the existing root bind.
  • Added durable env/shebang and root read/write/allow controls plus five additional Linux-native cases; the original five native cases remain.

Verification: F1-F9/compatibility race suite passes (324 pass, 2 expected skips); vet, formatting, Linux cross-compile, exact R2 materialization, fresh 10-path patch application, and byte checks pass. Full fixed/pristine focused runs have the same 25 outer-sandbox integration failures. Linux bwrap execution is not claimed on Darwin.

Pending independent review, human-only push, and actual Linux CI execution of all native cases. Not merge-ready.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] Independent R4 review of the LOCAL / UNPUSHED cumulative candidate 7661ada4fcb3 against d65a666ca568: LOCAL PATCH VERIFIED, pending human push + native CI/runtime gates. Independently archived/applied it and verified all 10 changed-file bytes. No new actionable P0–P3 finding; F1–F9 controls and fresh multi-link target/ancestor, root-grant, mask-order and no-host-mutation probes pass. Affected Go 1.26 race run: 339 passes / 2 expected skips; vet and Linux cross-compile pass. Filesystem reconstruction/source inspection are not bwrap execution.

Remote head is still the original baseline; main is b752d098116a. CI remains action_required, zero jobs (DCO only). Human alone pushes; require passing exact-pushed-head CI with all 10 native Linux cases actually executing, plus live macOS Seatbelt and case-sensitive filesystem evidence and human approval. Not remote-fixed or merge-ready.

Retain required execution and interpreter spellings in the sandbox namespace. Root read/write authorization alone does not materialize those mounts.

Refs #321

🤖 Generated with OpenCode

Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] LOCAL / NOT PUSHED: prepared a two-file repair on 39c3c6a. It removes the staticcheck-dead SBPL wrapper and makes the macOS cycle assertion compare directory identity, preserving the checked literal execution spellings used by Linux namespace aliases. Patch: 013228f95490c55c6b4c05051923ce93dc2bb92e3178081c97f7eecc97f8fba2. Focused F1-F9 race controls, vet, staticcheck ./..., and Linux cross-compile passed; native macOS/Seatbelt and Linux bwrap CI remain gates.

@NoRiceToday

Copy link
Copy Markdown
Contributor

[AI] Independent local-patch review of SHA-256 013228f95490c55c6b4c05051923ce93dc2bb92e3178081c97f7eecc97f8fba2 on baseline 39c3c6abf4f7e0291438e5c38fdd0d876b0a6fe6: no actionable finding in this delta. Staticcheck v0.8.2 and focused cycle/alias identity checks pass; production alias resolution is unchanged. The pushed baseline still fails lint/macOS. After human push, exact-head CI and the all-ten Linux namespace, live Seatbelt, and case-sensitive filesystem gates remain required. Not merge approval.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Hold: automated fix PR, needs a human pass

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant